﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Afru
{
    public class Camera2D
    {
        #region Variables

        #region Private variables

        protected float _zoom;      // Camera Zoom
        protected float _rotation;  // Camera Rotation 
        protected Vector2 _screenDimensions;
        
        #endregion

        #region Public variables

        public Matrix _transform;   // Matrix Transform
        public Vector2 _pos;        // Camera Position
        
        #endregion

        #endregion

        #region Constructor

        public Camera2D(Vector2 ScreenDimensions)
        {
            _zoom = 1.0f;
            _rotation = 0.0f;
            _pos = Vector2.Zero;
            _screenDimensions = ScreenDimensions;
        }

        #endregion

        #region Class Functions

        #region Get and Set functions

        //Gets and sets the zoom
        public float Zoom
        {
            get { return _zoom; }
            set { _zoom = value; if (_zoom < 0.1f) _zoom = 0.1f; } // Negative zoom will flip image
        }

        //Gets and sets the rotation
        public float Rotation
        {
            get { return _rotation; }
            set { _rotation = value; }
        }

        //Gets and sets the position
        public Vector2 Pos
        {
            get { return _pos; }
            set { _pos = value; }
        }

        #endregion

        #region Functions to set the camera variables

        /// <summary>
        /// Move the camera by a vector2 amount
        /// </summary>
        /// <param name="amount">The amount to move the camera by</param>
        public void Move(Vector2 amount)
        {
            _pos += amount;
        }

        /// <summary>
        /// Rotate the camera by an angle in radians
        /// </summary>
        /// <param name="AngleInRadians">The angle in radians to rotate by</param>
        public void RotateByRadians(float AngleInRadians)
        {
            _rotation += AngleInRadians;
        }

        /// <summary>
        /// Rotate the camera by an angle in degrees
        /// </summary>
        /// <param name="AngleInDegrees">The angle in degrees to rotate by</param>
        public void RotateByDegrees(float AngleInDegrees)
        {
            _rotation += MathHelper.ToRadians(AngleInDegrees);
        }

        /// <summary>
        /// Modify the camera zoom 
        /// </summary>
        /// <param name="ZoomValue">The amount of zoom to apply to the camera</param>
        public void SetZoom(float ZoomValue)
        {
            _zoom += ZoomValue;
        }

        #endregion

        #region Camera matrix functions

        /// <summary>
        /// Get the camera transformation with rotation around the X axis
        /// </summary>
        /// <returns>Camera transformation</returns>
        public Matrix GetTransformationX()
        {
            _transform = Matrix.CreateTranslation(new Vector3(-_pos.X - (_screenDimensions.X / 2), -_pos.Y - (_screenDimensions.Y / 2), 0)) *
                            Matrix.CreateRotationX(Rotation) *
                            Matrix.CreateScale(new Vector3(Zoom)) *
                            Matrix.CreateTranslation(new Vector3(_screenDimensions.X * 0.5f, _screenDimensions.Y * 0.5f, 0));
            return _transform;
        }

        /// <summary>
        /// Get the camera transformation with rotation around the Y axis
        /// </summary>
        /// <returns>Camera transformation</returns>
        public Matrix GetTransformationY()
        {
            _transform = Matrix.CreateTranslation(new Vector3(-_pos.X - (_screenDimensions.X / 2), -_pos.Y - (_screenDimensions.Y / 2), 0)) *
                            Matrix.CreateRotationY(Rotation) *
                            Matrix.CreateScale(new Vector3(Zoom)) *
                            Matrix.CreateTranslation(new Vector3(_screenDimensions.X * 0.5f, _screenDimensions.Y * 0.5f, 0));
            return _transform;
        }

        /// <summary>
        /// Get the camera transformation with rotation around the Z axis
        /// </summary>
        /// <returns>Camera transformation</returns>
        public Matrix GetTransformationZ()
        {
            _transform = Matrix.CreateTranslation(new Vector3(-_pos.X - (_screenDimensions.X / 2), -_pos.Y - (_screenDimensions.Y / 2), 0)) *
                            Matrix.CreateRotationZ(Rotation) *
                            Matrix.CreateScale(new Vector3(Zoom)) *
                            Matrix.CreateTranslation(new Vector3(_screenDimensions.X * 0.5f, _screenDimensions.Y * 0.5f, 0));
            return _transform;
        }

        /// <summary>
        /// A special version of the GetTransformationZ() function that is used to draw the level class
        /// </summary>
        /// <returns>A transformation matrix</returns>
        public Matrix GetTransformationZLevel()
        {
            _transform = Matrix.CreateTranslation(new Vector3(-_pos.X, -_pos.Y, 0)) *
                            Matrix.CreateRotationZ(Rotation) *
                            Matrix.CreateScale(new Vector3(Zoom)) *
                            Matrix.CreateTranslation(new Vector3(_screenDimensions.X * 0.5f, _screenDimensions.Y * 0.5f, 0));
            return _transform;
        }

        /// <summary>
        /// Get the camera transformation with rotation around the Z axis
        /// </summary>
        /// <returns>Camera transformation</returns>
        public Matrix GetTransformationZNoTranslation()
        {
            _transform = Matrix.CreateRotationZ(Rotation) * Matrix.CreateScale(new Vector3(Zoom));
            return _transform;
        }

        /// <summary>
        /// Get the camera transformation rotating around the X axis and taking into account a parallax offset value
        /// </summary>
        /// <param name="Parallax">The amount of parallax to apply to the transformation. More parallax = more delay in movement</param>
        /// <returns></returns>
        public Matrix GetTransformationXParallax(Vector2 Parallax)
        {
            Vector2 Origin = this.GetCameraCenter();
            _transform = Matrix.CreateTranslation(new Vector3(-_pos * Parallax, 0.0f)) *
                           Matrix.CreateTranslation(new Vector3(-Origin, 0.0f)) *
                           Matrix.CreateRotationX(Rotation) *
                           Matrix.CreateScale(Zoom, Zoom, 1) *
                           Matrix.CreateTranslation(new Vector3(Origin, 0.0f));

            return _transform;
        }

        /// <summary>
        /// Get the camera transformation rotating around the Y axis and taking into account a parallax offset value
        /// </summary>
        /// <param name="Parallax">The amount of parallax to apply to the transformation. More parallax = more delay in movement</param>
        /// <returns></returns>
        public Matrix GetTransformationYParallax(Vector2 Parallax)
        {
            Vector2 Origin = this.GetCameraCenter();
            _transform = Matrix.CreateTranslation(new Vector3(-_pos * Parallax, 0.0f)) *
                           Matrix.CreateTranslation(new Vector3(Origin, 0.0f)) *
                           Matrix.CreateRotationY(Rotation) *
                           Matrix.CreateScale(Zoom, Zoom, 1) *
                           Matrix.CreateTranslation(new Vector3(Origin, 0.0f));

            return _transform;
        }

        /// <summary>
        /// Get the camera transformation rotating around the Z axis and taking into account a parallax offset value
        /// </summary>
        /// <param name="Parallax">The amount of parallax to apply to the transformation. More parallax = more delay in movement</param>
        /// <returns></returns>
        public Matrix GetTransformationZParallax(Vector2 Parallax)
        {
            Vector2 Origin = this.GetCameraCenter();
            _transform = Matrix.CreateTranslation(new Vector3(-_pos * Parallax, 0.0f)) *
                           Matrix.CreateTranslation(new Vector3(-Origin, 0.0f)) *
                           Matrix.CreateRotationZ(Rotation) *
                           Matrix.CreateScale(Zoom, Zoom, 1) *
                           Matrix.CreateTranslation(new Vector3(-Origin, 0.0f));

            return _transform;
        }

        #endregion

        #region Camera view functions

        /// <summary>
        /// Checks to see if a rectangle is in the view of the camera
        /// </summary>
        /// <param name="rotatedRectangle">The rectangle to check</param>
        /// <returns></returns>
        public bool IsInCameraView(RotatedRectangle rotatedRectangle)
        {
            RotatedRectangle tmpRec = new RotatedRectangle(new Rectangle((int)_pos.X, (int)_pos.Y, (int)_screenDimensions.X, (int)_screenDimensions.Y), _rotation);

            if (tmpRec.Intersects(rotatedRectangle))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Checks to see if a rectangle is in the view of the camera plus or minus an offset
        /// </summary>
        /// <param name="rotatedRectangle">The rectangle to check</param>
        /// <param name="OffSet">The value to alter the camera bounds by</param>
        /// <returns></returns>
        public bool IsInCameraView(RotatedRectangle rotatedRectangle, Vector2 OffSet)
        {
            RotatedRectangle tmpRec = new RotatedRectangle(
                new Rectangle((int)(_pos.X - OffSet.X), (int)(_pos.Y - OffSet.Y), (int)(_screenDimensions.X + OffSet.X), (int)(_screenDimensions.Y + OffSet.Y)),
                _rotation);

            if (tmpRec.Intersects(rotatedRectangle))
                return true;
            else
                return false;
        }

        /// <summary>
        /// Finds the center of the camera
        /// </summary>
        /// <returns>A vector2 with the co-ordinates of the camera center</returns>
        public Vector2 GetCameraCenter()
        {
            return new Vector2(_pos.X + _screenDimensions.X / 2, _pos.Y + _screenDimensions.Y / 2);
        }

        #endregion

        #region Positional functions

        public Vector2 ToWorldLocation(Vector2 position)
        {
            return Vector2.Transform(position, Matrix.Invert(_transform));
        }

        public Vector2 ToLocalLocation(Vector2 position)
        {
            return Vector2.Transform(position, _transform);
        } //TODO Fix dodgy camera position functions

        #endregion

        #endregion
    }
}
